package gologger

import (
	"fmt"
	"os"
	"path"
	"time"
)

//缓存队列长度
const logMsgChannMaxSize = 50000

type FileLogger struct {
	Level       LogLevel
	filePath    string
	logFileName string
	fileObj     *os.File
	errFileObj  *os.File
	MaxSize     int64
	logMsgChan  chan *logMsg
}

//单条log信息
type logMsg struct {
	Level     LogLevel
	funcName  string
	fileName  string
	lineNo    int
	msg       string
	timestamp string
}

//构造函数
func NewFileLogger(levelStr, fp, fn string, maxSize int64) *FileLogger {
	level, err := paseLogLevel(levelStr)
	if err != nil {
		panic(err)
	}
	f := &FileLogger{
		Level:       level,
		filePath:    fp,
		logFileName: fn,
		MaxSize:     maxSize,
		logMsgChan:  make(chan *logMsg, logMsgChannMaxSize),
	}
	err = f.initFile()
	if err != nil {
		panic(err)
	}

	//开启并发写入
	go f.WriteToFile()
	return f

}

//初始化日志文件
func (f *FileLogger) initFile() error {
	fn := path.Join(f.filePath + f.logFileName)
	fileObj, err := os.OpenFile(fn+".log", os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
	if err != nil {
		fmt.Printf("open logfile %s error", fn)
		return err
	}
	errFileObj, err := os.OpenFile(fn+".err", os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
	if err != nil {
		fmt.Printf("open error logfile %s error", fn+".err")
		return err
	}
	f.fileObj = fileObj
	f.errFileObj = errFileObj
	return nil
}

//日志开关
func (f *FileLogger) enable(level LogLevel) bool {
	return level >= f.Level
}

//文件切割
func (f *FileLogger) splitFile(file *os.File) (*os.File, error) {
	fn := file.Name()
	logFile := path.Join(f.filePath, file.Name())
	bakLogFile := fmt.Sprintf("%s", fn+time.Now().Format(time.Now().Format("20060102030405"))+".bak")

	err := file.Close()
	if err != nil {
		fmt.Printf("close %s file faild.", fn)
		return nil, err
	}
	err = os.Rename(logFile, bakLogFile)
	if err != nil {
		return nil, err
	}
	fileObj, err := os.OpenFile(logFile, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
	if err != nil {
		fmt.Println("open logfile error", fileObj)
		return nil, err
	}
	return fileObj, nil
}

//检查文件大小是否超过设定阈值
func (f *FileLogger) checkSize(file *os.File) bool {
	FileInfo, err := file.Stat()
	if err != nil {
		fmt.Println(err)
		return false
	}
	return FileInfo.Size() > f.MaxSize
}

func (f *FileLogger) WriteToFile() {
	for {
		if f.checkSize(f.fileObj) {
			file, err := f.splitFile(f.fileObj)
			if err != nil {
				return
			}
			f.fileObj = file
		}
		select {
		case logData := <-f.logMsgChan:
			_, err := fmt.Fprintf(f.fileObj, "%s [%s] [file: %s/%s line: %d] %s \n", logData.timestamp, getLogString(logData.Level), logData.funcName, logData.fileName, logData.lineNo, logData.msg)
			if err != nil {
				fmt.Println(err)
				return
			}
			if logData.Level >= ERROR {
				if f.checkSize(f.errFileObj) {
					file, err := f.splitFile(f.errFileObj)
					if err != nil {
						return
					}
					f.errFileObj = file
				}
				_, err := fmt.Fprintf(f.errFileObj, "%s [%s] [file: %s/%s line: %d] %s \n", logData.timestamp, getLogString(logData.Level), logData.funcName, logData.fileName, logData.lineNo, logData.msg)
				if err != nil {
					fmt.Println(err)
					return
				}
			}
		default:
			fmt.Println("")

		}

	}

}

//格式化输出
func (f *FileLogger) logFotmatOutput(lv LogLevel, fotmat string, a ...interface{}) {
	if f.enable(lv) {
		msg := fmt.Sprintf(fotmat, a...)
		funcName, fileName, lineNo, timestamp := getInfo(3)

		logTmp := &logMsg{
			Level:     lv,
			funcName:  funcName,
			fileName:  fileName,
			lineNo:    lineNo,
			msg:       msg,
			timestamp: timestamp,
		}
		select {
		case f.logMsgChan <- logTmp:
		default:
			fmt.Println("logMsgchannel is full,please chanage logMsgChannMaxSize ～！！！")
		}

	}

}

func (f *FileLogger) DEBUG(fotmat string, a ...interface{}) {
	f.logFotmatOutput(DEBUG, fotmat, a...)
}
func (f *FileLogger) INFO(fotmat string, a ...interface{}) {
	f.logFotmatOutput(INFO, fotmat, a...)
}
func (f *FileLogger) WARNING(fotmat string, a ...interface{}) {
	f.logFotmatOutput(WARNING, fotmat, a...)
}

//错误日志
func (f *FileLogger) ERROR(fotmat string, a ...interface{}) {
	f.logFotmatOutput(ERROR, fotmat, a...)
}
func (f *FileLogger) FATAL(fotmat string, a ...interface{}) {
	f.logFotmatOutput(FATAL, fotmat, a...)
}
